# 导入需要的模块
from PyQt5.QtCore import QDateTime, QTimer, Qt

from PyQt5.QtWidgets import (QApplication, QGridLayout, QHBoxLayout, QVBoxLayout,
                             QGroupBox, QLabel, QCheckBox, QRadioButton, QComboBox,
                             QLineEdit, QTextEdit, QDialog, QPushButton,
                             QSlider, QScrollBar, QSpinBox, QTableView, QTabWidget,
                             QProgressBar, QDial, QWidget, QStyleFactory, QSizePolicy,
                             QTableWidget, QDateEdit, QDateTimeEdit)
# 当然在导入时候可以是同通配符* 来简便导入
# from PyQt5.QtWidgets import *


# 下面我们通过一个小的案例来了解一下不同组件以及简单的事件触发

# 先定义一个WidgetGallery类，该类继承了QDialog类
class WidgetGallery(QDialog):

    # 下面我们重写其init方法,其中parent=None表示不使用父类的方法
    def __init__(self, parent=None):

        # 首先设置自定义样式
        # 1.使用super方法 来实现调用WidgetGallery类的时候，不使用父类的初始方法，而是使用WidgetGallery自身的init方法
        super(WidgetGallery, self).__init__(parent)
        # 2.定义一个自身的模板
        self.originalPalette = QApplication.palette()
        #############################################

        # 设计组件 #
        # 样式下拉列表 #    （达到的效果：下拉列表，并且选择不同样式）
        # 接下来构建页面的第一行--下拉列表
        style_combobox = QComboBox()
        # 而下拉列表中一定有数据绑定，所以我们需要给其一些数据
        style_combobox.addItems(QStyleFactory.keys())  # 将样式工厂中keys 添加给 定义的下拉列表中
        # 注意上面添加数据基本上多个数据，那么对应的应该是addItems 注意有s 否则会报错

        # ## 下拉列表的事件触发（下拉列表选择不同的样式，进行更改）
        style_combobox.activated[str].connect(self.changeStyle)
        # 样式下拉列表（style_combobox）激活（.activated）哪个字符串（[str]）就会触发（.connect）自身样式的改变（self.changeStyle）

        # 下拉列表会有列表自身的标签，那么我就需要定义对应的标签
        style_label = QLabel('样式')
        # 上面这个label要在谁前面显示呢，那就需要对其进行定义（也就是定位）
        style_label.setBuddy(style_combobox)  # 让style_label标签显示在style_combobox前面

        # checkbox组件 #  （达到的效果：勾选框）
        self.use_stander_checkbox = QCheckBox('使用标准样式')
        # 下面是将定义好的checkbox设置为默认选中的状态
        self.use_stander_checkbox.setChecked(True)
        # 然后我们再设计一个checkbox的禁用组件
        disable_widget_checkbox = QCheckBox('禁用组件')

        # ## 界面第一行可勾选组件的事件触发（选中和不选中会改变模板）
        self.use_stander_checkbox.toggled.connect(self.changePalette)
        # 通过use_stander_checkbox是否勾选（.toggled）的状态来触发（.connect）更改模板事件（self.changePalette）

        #################################################################################

        # 设置布局 #
        # 有了组件之后，我们就需要对组件进行布局
        # 顶端布局 (上面的下拉列表是布置在一行当中，所以对应布局就应该是水平布局)
        top_layout = QHBoxLayout()  # 这里就是定义一个布局文件top_layout
        # 下面就是将之前的组件添加到这个布局文件中去
        top_layout.addWidget(style_label)  # 将标签添加到这个布局文件中
        top_layout.addWidget(style_combobox)  # 将下拉列表添加到这个布局文件中
        top_layout.addWidget(self.use_stander_checkbox)  # 将checkbox添加到这个布局文件中
        top_layout.addWidget(disable_widget_checkbox)  # 将禁用组件添加到布局文件中

        # 有了布局之后，我们想要将其呈现出来怎么办，那么就需要在当前的self对象中的setLayout()中添加这个布局
        # self.setLayout(top_layout)    # 这里是指加载出第一行的布局，如果想要将下方的布局都加载在页面中

        # 想要将下方的布局整合进来，那么我们可以先定义一个网格布局
        # 先是调用生成左上角的组
        self.createTopLeftGroupBox()  # 理解：这里是同一个类（class WidgetGallery(QDialog)）当中一个函数方法（__init__）调用另一个函数方法（createTopLeftGroupBox()）
        # 再是调用生成的右上角的组
        self.createTopRightGroupBox()
        # 再创建生成左下角的组
        self.createBottomLeftTabWidget()
        # 继续创建右下角的组
        self.createBottomRightGroupBox()
        # 最后创建进度条
        self.createProgressBar()
        # 创建一个网格对象
        main_layout = QGridLayout()
        # 将top_layout布局添加到表格布局
        main_layout.addLayout(top_layout, 0, 0, 1, 2)
        # 这里是将top_layout布局添加在网格布局的 第0行 第0列，1表示“行”不合并，2表示将2“列”合并

        # 将self.top_left_group布局加入到网格布局
        main_layout.addWidget(self.top_left_group, 1, 0)
        # 注意：这里相当于是将左上角的组看成一个整体的窗口部件

        # 将self.top_right_group布局加入到网格布局
        main_layout.addWidget(self.top_right_group, 1, 1)
        # 这里是将右上角的组添加到网格布局的 第二行（1），第二列（1）

        # 将self.buttom_left_tabwidget布局加入到网格布局中去
        main_layout.addWidget(self.buttom_left_tabwidget, 2, 0)

        # 将self.bottom_right_group控件布局加入到网格布局中去
        main_layout.addWidget(self.bottom_right_group, 2, 1)

        # 将self.ProgressBar控件添加到网格布局中去
        main_layout.addWidget(self.Progressbar, 3, 0, 1, 2)

        # 最后就是将网格布局(main_layout)添加到自身布局self.setLayout
        self.setLayout(main_layout)
        ##################################################################

        # 设置界面的标题 #
        # 下面我们可以设置一下其title
        self.setWindowTitle('基本组件')

    # 上面的初始化方法__init__相当于是定义了界面的第一行
    # 接下来我们设计界面 左上角：第一组的基本组件
    # 补充知识：再类中定义的def 叫方法，在类外部定义的def叫函数
    def createTopLeftGroupBox(self):
        # 这里的设计原则是：从大往小了做
        self.top_left_group = QGroupBox('第一组')  # 即将左上角的一组定义为QGroupBox()
        radiobutton1 = QRadioButton('单选1')  # 设计这一组内的单选1
        radiobutton2 = QRadioButton('单选2')  # 设计这一组内的单选2
        radiobutton3 = QRadioButton('单选3')  # 设计这一组内的单选3
        # 设计checkbox
        checkbox = QCheckBox('Tri-state checkbox')
        checkbox.setTristate(True)  # 定义这个checkbox的多种属性状态为真（也就是启用多种状态）
        checkbox.setCheckState(Qt.PartiallyChecked)  # 这里就是让多选框有多种状态的展示

        # 下面就是进行布局(垂直布局)
        layout = QVBoxLayout()
        layout.addWidget(radiobutton1)  # 将radiobutton1添加到布局中，另外addWidget 就是 添加组件的意思
        layout.addWidget(radiobutton2)  # 将radiobutton2添加到布局中
        layout.addWidget(radiobutton3)  # 将radiobutton3添加到布局中
        layout.addWidget(checkbox)  # 将checkbox添加到布局中
        # 注意接下里要搞清楚是谁要使用这个布局，是左上角的组（self.top_left_group）使用
        self.top_left_group.setLayout(layout)
        # 那么设计完左上角的组之后，我们需要将其加载到整个的init当中去

    # 接着继续做右上角的组（第二组）
    def createTopRightGroupBox(self):
        # 先定义一个组（QGroupBox）的对象，并且命名为‘第二组’
        self.top_right_group = QGroupBox('第二组')
        # 而在第二组中有三种形式的按钮
        # 默认按钮 #
        default_button = QPushButton('默认Button')  # 创建一个默认按钮对象
        default_button.setDefault(True)  # 定义默认按钮对象的初始状态为默认状态
        # 开关按钮 #
        toggle_button = QPushButton('开关Button')  # 创建一个开关按钮对象
        toggle_button.setCheckable(True)  # 设置开关按钮对象的状态：可以被选中状态
        toggle_button.setChecked(True)  # 设置开关按钮对象的默认状态为：被选中的状态
        # flat_button # （不突起的button）
        flat_button = QPushButton('FlatButton')  # 创建一个flat_button对象
        flat_button.setFlat(True)  # 设置flat_button的初始状态

        # 有了组件之后就一定要有布局
        # 布局 #
        layout = QVBoxLayout()  # 创建一个垂直布局对象
        layout.addWidget(default_button)  # 将默认按钮组件添加进布局
        layout.addWidget(toggle_button)  # 将开关按钮组件添加到布局中
        layout.addWidget(flat_button)  # 将flat-button组件添加到布局中
        # 下面就是要定义这个布局是谁来调用，分析可得，这个布局应该是右上角的第二组来调用
        self.top_right_group.setLayout(layout)
        # self->(自身) top_right_group->(右上角第二组) setLayout->(设置布局) layout->(之前定义好的布局)

        # 既然创建好了右上角的第二组 那么就需要将其添加到init方法当中去

    # 继续左下角的组（构建一个选项卡，一个显示table一个显示textedit）选项卡：可以在有限的空间里显示更多的内容
    def createBottomLeftTabWidget(self):
        # 首先创建一个选项卡对象
        self.buttom_left_tabwidget = QTabWidget()
        # 接下来为了选项卡有更好的显示效果，可以设置其默认大小
        self.buttom_left_tabwidget.setSizePolicy(QSizePolicy.Preferred,
                                                 QSizePolicy.Ignored)  # QSizePolicy.Preferred表示QSizePolicy里的自动调节大小
        # 有了选项卡之后，就要设置每一个标签（一个是table，一个是textedit）
        tab1 = QWidget()  # 创建第一个标签（table）对象
        # 每一个标签内是有内容的（内容一个是表格、一个是textedit）
        tablewidget = QTableWidget(10, 10)  # 创建一个表格对象，定义其大小是10行10列
        # 有了组件就一定有布局
        tab1hbox = QHBoxLayout()  # 定义一个水平布局对象
        tab1hbox.addWidget(tablewidget)  # 将表格组件添加到水平布局当中
        # 有了布局，那要知道是谁调用这个布局呢，经分析，是tab1这个标签对象使用这个水平布局
        tab1.setLayout(tab1hbox)

        # 下面我们就可以做第二个标签（textedit）
        tab2 = QWidget()  # 创建一个组件对象(标签2)
        textedit = QTextEdit()  # 创建文本编辑对象
        textedit.setPlainText('菩提本无树，\n'
                              '明镜亦非台。\n'
                              '本来无一物，\n'
                              '何处染尘埃。\n')  # 赋予文本编辑对象（textedit）内容
        # 标签内的组件有了，那么就一定要有布局
        tab2hbox = QHBoxLayout()  # 创建tab2的水平布局对象
        tab2hbox.addWidget(textedit)  # 将文本编辑对象（组件）添加到水平布局当中去
        tab2.setLayout(tab2hbox)  # 设置tab2标签的布局为水平布局

        # 有了标签（table,textedit）布局，那么还需要将table、textedit布局添加到选项卡当中去
        self.buttom_left_tabwidget.addTab(tab1, '表格')
        self.buttom_left_tabwidget.addTab(tab2, '文本')

    # 还有右下角的组
    def createBottomRightGroupBox(self):
        self.bottom_right_group = QGroupBox('第三组')  # 创建（右下角）组对象

        # 首先创建一个单行输入的文本框（用来输入密码）
        lineedit = QLineEdit('strs3')  # 创建一个单行文本对象
        lineedit.setEchoMode(QLineEdit.Password)  # 由于是输入密码的，所以将单行文本的属性设置为行文本编辑的密码模式

        # 创建一个可调节的输入框spinbox
        spinbox = QSpinBox(self.bottom_right_group)  # 创建一个spinbox对象
        # 注意创建spinbox的时候，要注明在那个组里创建，self.bottom_right_group表示在右下角方法自身的bottom_right_group组中创建
        spinbox.setValue(20)  # 设置spinbox组件的的初始显示值

        # 创建可调节时间的显示器
        datetimeedit = QDateTimeEdit(self.bottom_right_group)  # 创建时间调节组件
        # 注意：这里也需要说明其安装的位置（self.bottom_right_group）
        # 另外，如果不设置其初始值，显示时间是默认的2000/1/1 0:00，但是我们想要显示的肯定是当前时间
        datetimeedit.setDateTime(QDateTime.currentDateTime())
        # 对时间组件（datetimeedit）设置时间（setDateTime），采用的是QDateTime当中的当前时间（currentDateTime()）

        # 创建一个滑块组件（可以用来调节进度，调节音量之类的）
        slide = QSlider(Qt.Horizontal, self.bottom_right_group)  # 创建一个滑块组件对象
        # 注意：滑块有水平和垂直两种，我们是在其定义的时候就设置其属性（在QSlider()内设置）
        slide.setValue(50)  # 设置滑块的滑动值（范围）

        # 创建一个滚动条组件（一般滚动条是和别的（不具备滚动功能）组件一起使用的）
        scrollbar = QScrollBar(Qt.Horizontal, self.bottom_right_group)  # 创建一个滚动条组件对象
        # 注意：滚动条也是分两种，水平和垂直，也是在创建对象时候就要说明的
        scrollbar.setValue(40)  # 设置滚动条的滚动值（范围）

        # 创建一个dial（就是一个可调节的旋钮）
        dial = QDial(self.bottom_right_group)  # 创建一个dial组件对象
        dial.setValue(20)  # 设置dial的默认值 （另外旋转钮上面应该标有刻度）
        dial.setNotchesVisible(True)  # 这里是将旋钮的刻度显示出来

        # 有了组件就一定有布局
        layout = QGridLayout()  # 创建一个网格布局
        layout.addWidget(lineedit, 0, 0, 1, 2)  # 将单行文本组件添加到网格布局中（设置在第一行第一列，占单行两列空间）
        layout.addWidget(spinbox, 1, 0, 1, 2)  # 将spinbox组件添加到网格布局中
        layout.addWidget(datetimeedit, 2, 0, 1, 2)  # 将时间显示器组件添加到网格布局中
        layout.addWidget(slide, 3, 0)  # 将滑块组件添加到网格布局中（这里没有写其所占空间大小是为了后面的处理）
        layout.addWidget(scrollbar, 4, 0)  # 将滚动条组件添加到网格布局中
        layout.addWidget(dial, 3, 1, 2, 1)  # 将dial组件添加到网格布局中（注意，这里是将dial组件放在第四行第二列处，所占空间为两行一列）

        # 有了布局那就要知道是谁调用这个布局，显然这里是bottom_right_group 这个对象使用
        self.bottom_right_group.setLayout(layout)

    # 最后还有底部的进度条
    def createProgressBar(self):
        self.Progressbar = QProgressBar()  # 将自身的ProgressBar定义为一个QProgressBar()对象
        self.Progressbar.setRange(0, 1000)  # 设置进度条的总范围
        self.Progressbar.setValue(0)  # 设置进度条的初始值

        # 而我们怎么样显示进度条的进度呢，一般我们使用timer来完成
        timer = QTimer(self)  # 创建一个timer对象(对自己生成)
        # 下面是关键!!! 也就是qt当中的事件处理的一种机制
        timer.timeout.connect(self.advanceProgressBar)  # 这里的connect就是连接的一个函数或者方法
        # 注意上面的connect后面只写self.方法名   方法名不写参数（也就是方法名后面不写括号）
        timer.start(1000)  # 花1000毫秒（1秒）做一个这样的connect处理

    # 创建一个方法，供timer连接
    def advanceProgressBar(self):
        cur_val = self.Progressbar.value()  # 得到进度条的当前值
        max_value = self.Progressbar.maximum()  # 得到进度条的最大值
        self.Progressbar.setValue(int(cur_val + (max_value - cur_val) / 100))  # 设置一个进度值

    # 改变模板（界面第一行的选择按钮的事件处理）
    def changePalette(self):
        # 如果use_stander_checkbox是被选中的状态（.isChecked()）
        if self.use_stander_checkbox.isChecked():
            # 那么QApplication的样式（.setPalette）就执行QApplication当中的样式（.style()）中的标准模板（standardPalette()）
            QApplication.setPalette(QApplication.style().standardPalette())
        else:
            # 否则QApplication的模板（.setPalette）就使用我们自己定义的模板（self.originalPalette）
            QApplication.setPalette(self.originalPalette)

    # 改变样式（界面第一行当中的style的事件触发）
    def changeStyle(self, style_name):
        # QApplication的样式（.setStyle）是根据（style_name）来创建（.create）样式工厂（QStyleFactory）中的样式
        QApplication.setStyle(QStyleFactory.create(style_name))
        # 样式改变之后就需要改变相应的模板
        self.changePalette()


# 测试入口
if __name__ == '__main__':
    import sys

    # python中的sys是提供了一系列有关python运行环境的变量和函数的模块，
    # 如sys.argv函数实现从程序外部向程序传递参数；
    # sys.platform函数用于获取当前系统平台

    app = QApplication(sys.argv)
    gallery = WidgetGallery()  # 也就是定义了一个控件平台对象(这里的WidgetGallery()是我们上面定义的类)
    # widget(控件的意思)-gallery(画廊的意思) 合起来就是展示控件平台
    gallery.show()  # 将控件平台显示出来
    sys.exit(app.exec_())
    # 先表示动态执行app代码（app.exec_()），再退出程序(sys.exit())